import sys, logging
import pygame, constants
from pygame.locals import *
from World import character, world, projectile, string, text, world_object, score
from generator import StringGenerator, ProjectileGenerator
import observable
import jukebox
import os
import data

if not pygame.mixer:
    logging.getLogger(constants.LOGGER_NAME).fatal("Error loading mixer")
    sys.exit()
if not pygame.font:
    logging.getLogger(constants.LOGGER_NAME).fatal("Error loading fonts")
    sys.exit()

class Engine(object, observable.Observable):

    def __init__(self, width=constants.SCREEN_WIDTH, height=constants.SCREEN_HEIGHT, disable_sounds=False):
        observable.Observable.__init__(self)
        self.log = logging.getLogger(constants.LOGGER_NAME)
        self.log.debug("Initializing Engine")
        self.fullscreen = False
        self._postponed_events = {}
        self._menu = []
        self.init_pygame()
        self._looping = True
        self.clock = pygame.time.Clock()
        self.hero = None
        self.width = width
        self.height = height
        self.screen = pygame.display.set_mode((self.width, self.height))
        self.world = world.World()
        self.string_generator = StringGenerator(self, self.world) 
        self.projectile_generator = ProjectileGenerator(self, self.world) 
        pygame.display.set_caption("strlen")
        self.disable_sounds = disable_sounds
        self._quit = False
        
        if self.disable_sounds:
            jukebox.juke.turn_off()
        else:
            jukebox.juke.init()
            pygame.mixer.music.set_endevent(pygame.USEREVENT)
            self.songs = [constants.HIP_HOP_MUSIC, constants.GHOSTLY_MUSIC, constants.JAZZ_ROCK_MUSIC]
            self.current_song = 0
            jukebox.juke.play_song(self.songs[self.current_song])

    def stop(self):
        self._looping = False

    def init_pygame(self):
        pygame.init()
        pygame.key.set_repeat(constants.KEY_REPEAT_INTERVAL, constants.KEY_REPEAT_INTERVAL)
        
    def init_world(self):
        self.world.empty()
        self.background = data.load_image('background.png')
        self._menu.append(text.Text("> strlen", x=20, y=50))
        self._menu.append(text.Text("Avoid the boulders", x=80, y=180))
        self._menu.append(text.Text("Jump from string to string...", x=80, y=220))
        self._menu.append(text.Text("The higher you go, the more points you score !", x=30, y=370))
        self.show_instructions(self._menu)
        self.show_highest_score(self._menu)

        for obj in self._menu:
            self.world.add_object(obj)
        self.call_later(self.init_game_world, wait=3)
        self.main_loop()

    def show_instructions(self, menu):
        instructions = ("F   =  toggle fullscreen","<-  =  jump left","->  =  jump right")
        for i in range(3):
            menu.append(text.Text(instructions[i], x=415, y=i*20 + 20, size=18, color=(200,200,250)))
        instructions_box = world_object.WorldObject()
        instructions_box.image = pygame.Surface((230,80))
        instructions_box.image.fill(0x000000)
        instructions_box.image.set_alpha(150)
        instructions_box.set_position(400, 10)
        instructions_box.z_index = -1
        instructions_box.set_scrollable(False)
        menu.append(instructions_box)

    def show_highest_score(self, menu):
        menu.append(text.Text("Highest Score: %s" % score.Score.get_highest_score(), x=20, y=80))

    def init_game_world(self):
        self._menu = []
        self.world.empty()
            
        self.hero = character.Character(100, 20)
        self.hero.set_world(self.world)
        self.hero.set_engine(self)
        self.hero.make_invincible()
        self.attach(self.hero)
        self.hero.attach(self)
        
        x, y = self.hero.position()
        rope = string.String(x, y-2, 70)
        self.hero.grab_string(rope)

        self.world.add_object(self.hero)
        self.world.add_object(rope)
        self.world.add_object(projectile.Projectile(angle = 300))

        self.string_generator.populate_world()
        self.projectile_generator.populate_world()

        self.world.all_strings()[0]
        self.world.add_object(text.Text("Jump up !", x=100, y=300, ttl=3, angle=60, speed=2, color=(220,220,0)))

        self.world.create_osd(self.hero)
        
    def main_loop(self):
        while self._looping:
            self.clock.tick(constants.FRAME_RATE)
            self.manage_events()
            self.update_objects()
            self.screen.blit(self.background, (0,0))
            self.render_objects()
            pygame.display.flip()

        # was restarted
        if not self._quit:
            self._looping = True
            self.init_world()

    def update_objects(self):
        if not self._menu:
            self.string_generator.populate_world()
            self.projectile_generator.populate_world()

        for this in self.world.objects():
            if this.should_scroll_down(): this.scroll_down()
            this.before_collisions()
            for that in self.world.objects():
                if type(this) == type(that): continue #optimization
                if this in that.collision_list():
                    this.collide(that)
                elif this.overlaps(that): #optimization: no check for this different than that because types are different
                    this.add_to_collision_list(that)
                    this.collide(that)

            this.tick_update()

        if self.hero:
            constants.VERTICAL_SCROLL_FACTOR = round(int(self.hero.points()) * constants.SPEED_TO_POINTS_RATIO, 1) * 0.01 + 1
        constants.MAX_PROJECTILES = constants.VERTICAL_SCROLL_FACTOR + 3
        constants.CLIMB_SPEED = constants.VERTICAL_SCROLL_FACTOR * 5
        constants.JUMP_SPEED = constants.VERTICAL_SCROLL_FACTOR * 2 + 3
        constants.MAX_STRING_LENGTH = int(constants.MAX_STRING_LENGTH_BASE - (constants.VERTICAL_SCROLL_FACTOR * 9))
        if constants.MAX_STRING_LENGTH <= constants.MIN_STRING_LENGTH: constants.MAX_STRING_LENGTH = constants.MIN_STRING_LENGTH

        self.world.cleanup(self.height)

    def render_objects(self):
        self.world.sort_objects()
        self.world.update_sprites()
        for obj in self.world.objects():
            self.screen.blit(obj.surface(), obj.position())
            for decoration in obj.get_decorations():
                self.screen.blit(decoration.surface(), decoration.position())
                
    def call_later(self, fn, wait=5):
        self._postponed_events[fn] = wait * constants.FRAME_RATE

    def manage_events(self):
        for event, timer in self._postponed_events.items():
            if timer <= 0: 
                event()
                del self._postponed_events[event]
            else:
                self._postponed_events[event] = (timer - 1)
            
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self._quit = True
                self._looping = False

            if event.type == pygame.KEYDOWN:
                self.notify(event=event)
                if event.key == pygame.K_f:
                    self.toggle_fullscreen()

            if event.type == pygame.USEREVENT:
                if not self.disable_sounds:
                    self.current_song += 1
                    if self.current_song >= len(self.songs):
                        self.current_song = 0
                    jukebox.juke.play_song(self.songs[self.current_song])

    def toggle_fullscreen(self):
        tmp = self.screen.convert()
        caption = pygame.display.get_caption()
        cursor = pygame.mouse.get_cursor()  # Duoas 16-04-2007 
        
        flags = self.screen.get_flags()
        bits = self.screen.get_bitsize()
        
        pygame.display.quit()
        self.init_pygame()
        
        if self.fullscreen:
            self.screen = pygame.display.set_mode((self.width, self.height))
            self.fullscreen = False
        else:
            self.screen = pygame.display.set_mode((self.width, self.height),flags^FULLSCREEN,bits)
            self.fullscreen = True

        self.screen.blit(tmp,(0,0))
        pygame.display.set_caption(*caption)
        pygame.key.set_mods(0) #HACK: work-a-round for a SDL bug??
        pygame.mouse.set_cursor( *cursor )  # Duoas 16-04-2007

    def get_screen(self):
        """The screen object will change when toggling fullscreen so we need to provide dynamic access to it"""
        return self.screen

    def update_from_observable(self, death=None, reposition=None):
        if death:
            current_score = self.hero.score()
            self.log.debug("hero is dead with score: %s" % current_score)
            jukebox.juke.play_sound(constants.NOO_SOUND)
            self.show_game_over_feedback(current_score)

            if current_score.is_new_high_score():
                current_score.save()
                self.show_score_feedback(current_score)

            def restart_engine():
                self._looping = False
            self.call_later(restart_engine, wait=6)
        if reposition:
            self.hero.reposition()
            self.show_lost_one_life_message()

    def show_game_over_feedback(self, score):
        game_over_callback = lambda: self.world.add_object(text.Text("GAME OVER", x=20, y=200, speed=3, ttl=8, size=56, color=(150, 0, 0)))
        score_callback = lambda: self.world.add_object(text.Text("Score: %s" % score, x=20, y=240, speed=3, ttl=9, size=36, color=(0, 100, 0)))
        self.call_later(game_over_callback, wait=0)
        self.call_later(score_callback, wait=1)
    
    def show_score_feedback(self, score):
        congrats_callback1 = lambda: self.world.add_object(text.Text("Congratulations !", x=20, y=140, speed=3, ttl=8, size=56, color=(0, 100, 100)))
        congrats_callback2 = lambda: self.world.add_object(text.Text("You've got a high score: %s" % score, x=2, y=180, speed=1, ttl=8, size=36, color=(10, 200, 100)))

        self.call_later(congrats_callback1, wait=3)
        self.call_later(congrats_callback2, wait=4)
    
    def show_lost_one_life_message(self):
        warning_callback = lambda: self.world.add_object(text.Text("You lost one life. Keep going!", x=2, y=180, speed=3, ttl=8, size=36, color=(150, 0, 0)))
        self.call_later(warning_callback, wait=0)
        
